﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Upreader.Application.Model;
using System.Linq.Expressions;
using System.Threading;
using System.Collections.Concurrent;
using ProtoBuf.Meta;

namespace Upreader.Infrastructure.ProtoBuf
{
    /// <summary>
    /// Implementation of the domainContext in protobuf.
    /// This uses a in-memory representation of the entire database
    /// </summary>
    public class ProtoBufDomainContext : IDomainContext
    {
        #region Fields

        private readonly DataContext _dataContext;

        private readonly Lazy<DataContextModelCollection<Server>> _serverCollection;
        private readonly Lazy<DataContextModelCollection<Download>> _downloadCollection;
        private readonly Lazy<DataContextModelCollection<File>> _fileCollection;
        private readonly Lazy<DataContextModelCollection<Segment>> _segmentCollection;

        private bool _isDisposed;

        #endregion // Fields

        #region Constructor

        public ProtoBufDomainContext(DataContext dataContext)
        {
            if (dataContext == null)
                throw new ArgumentNullException("dataContext");

            _dataContext = dataContext;

            TypeModel typeModel = ProtoBufMetaModelBuilder.Build();

            // setup modelCollections
            _serverCollection = new Lazy<DataContextModelCollection<Server>>(
                () => new DataContextModelCollection<Server>(dataContext, 
                                                             typeModel, 
                                                             () => dataContext.Servers,
                                                             () => dataContext.NextServerId++), false);

            _downloadCollection = new Lazy<DataContextModelCollection<Download>>(
                () => new DataContextModelCollection<Download>(dataContext, 
                                                               typeModel, 
                                                               () => dataContext.Downloads,
                                                               () => dataContext.NextDownloadId++), false);

            _fileCollection = new Lazy<DataContextModelCollection<File>>(
                () => new DataContextModelCollection<File>(dataContext, 
                                                           typeModel, 
                                                           () => dataContext.Files,
                                                           () => dataContext.NextFileId++), false);
            
            _segmentCollection = new Lazy<DataContextModelCollection<Segment>>(
                () => new DataContextModelCollection<Segment>(dataContext, 
                                                              typeModel, 
                                                              () => dataContext.Segments,
                                                              () => dataContext.NextSegmentId++), false);
        }

        #endregion // Constructor

        #region Properties

        /// <summary>
        /// Get a set of servers
        /// </summary>
        public IModelCollection<Server> Servers { get { ThrowOnDisposed(); return _serverCollection.Value; } }

        /// <summary>
        /// Get a set of servers
        /// </summary>
        public IModelCollection<Download> Downloads { get { ThrowOnDisposed(); return _downloadCollection.Value; } }

        /// <summary>
        /// Get a set of files
        /// </summary>
        public IModelCollection<File> Files { get { ThrowOnDisposed(); return _fileCollection.Value; } }

        /// <summary>
        /// Get a set of segments
        /// </summary>
        public IModelCollection<Segment> Segments { get { ThrowOnDisposed(); return _segmentCollection.Value; } }

        #endregion // Properties

        #region Methods

        /// <summary>
        /// Persists any changes back to the underlying datasources
        /// </summary>
        public IModelChanges SaveChanges()
        {
            ThrowOnDisposed();

            ProtoBufModelChanges changes = new ProtoBufModelChanges();

            if (_serverCollection.IsValueCreated)
            {
                changes.ChangedServers = _serverCollection.Value.SaveChanges();
            }

            if (_downloadCollection.IsValueCreated)
            {
                changes.ChangedDownloads = _downloadCollection.Value.SaveChanges();
            }

            if (_fileCollection.IsValueCreated)
            {
                changes.ChangedFiles = _fileCollection.Value.SaveChanges();
            }

            if (_segmentCollection.IsValueCreated)
            {
                changes.ChangedSegments = _segmentCollection.Value.SaveChanges();
            }

            return changes;
        }

        #endregion // Methods

        #region Helpers

        private void ThrowOnDisposed()
        {
            if (_isDisposed)
                throw new ObjectDisposedException("ProtoBufDomainContext");
        }

        #endregion // Helpers

        #region Disposable

        public void Dispose()
        {
            ThrowOnDisposed();

            _isDisposed = true;
        }

        #endregion // Disposable
    }
}
